#include "application.h"
#include "ota.h"
#include "bsp.h"
#include "protocol.h"
#include "audio_processor.h"
#include <stdlib.h>
#include <esp_err.h>
#include <esp_log.h>
#include <assert.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include "speaker_thing.h"

#define TAG "APP"

#define APPLICATION_LISTENING_EVENT_BIT (1 << 0)

char *state_str[] = {
    "STARTING",
    "IDLE",
    "ACTIVATING",
    "CONNECTING",
    "LISTENING",
    "SPEAKING",
};

typedef enum
{
    DEVICE_STARTING,
    DEVICE_IDLE,
    DEVICE_ACTIVATING,
    DEVICE_CONNECTING,
    DEVICE_LISTENING,
    DEVICE_SPEAKING,
} device_state_t;

typedef struct
{
    protocol_t *protocol;
    device_state_t state;
    thing_list_t *thing_list;

    TaskHandle_t audio_upload_task;
} application_t;

static application_t *s_application = NULL;

static void application_alert(char *msg)
{
    ESP_LOGW(TAG, "%s", msg);
}

static void application_ota()
{
    ota_t *ota = ota_create();
    assert(ota);
    while (1)
    {
        ota_check_new_version(ota);
        if (!ota->has_activation_code)
        {
            break;
        }
        char *msg = NULL;
        asprintf(&msg, "Activation code: %s", ota->activation_code);
        application_alert(msg);
        free(msg);
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
    ota_free(ota);
}

static void application_set_state(device_state_t state)
{
    if (s_application->state == state)
    {
        return;
    }
    ESP_LOGI(TAG, "State changed: %s -> %s", state_str[s_application->state], state_str[state]);
    bsp_led_set_state((bsp_led_state_t)state);
    s_application->state = state;
}
static void application_wakenet_handler(void *arg)
{
    switch (s_application->state)
    {
    case DEVICE_IDLE:
        application_set_state(DEVICE_CONNECTING);
        if (!protocol_is_audio_channel_open(s_application->protocol))
        {
            protocol_open_audio_channel(s_application->protocol);
        }
        protocol_send_hello(s_application->protocol);
        // 发IOT描述和状态JSON
        protocol_send_iot_descriptor(s_application->protocol, thing_list_get_descriptor_json(s_application->thing_list));
        protocol_send_iot_state(s_application->protocol, thing_list_get_state_json(s_application->thing_list));
        break;
    case DEVICE_SPEAKING:
        protocol_abort_speaking(s_application->protocol, PROTOCOL_ABORT_REASON_WAKEUP);
        break;
    default:
        break;
    }
    application_set_state(DEVICE_IDLE);
    protocol_send_wake_word(s_application->protocol, "你好小智");
}

static void application_audio_upload_task(void *arg)
{
    while (1)
    {
        size_t size = 0;
        void *buf = audio_processor_read(&size);
        if (size > 0)
        {
            if (s_application->state == DEVICE_LISTENING)
            {
                ESP_LOGD(TAG, "Uploading audio... %u", size);
                protocol_send_audio(s_application->protocol, buf, size);
            }

            free(buf);
        }
    }
}

static void application_vad_handler(void *arg, vad_state_t vad_state)
{
    if (vad_state == VAD_SPEECH && s_application->state == DEVICE_IDLE)
    {
        protocol_send_start_listening(s_application->protocol);
        application_set_state(DEVICE_LISTENING);
    }
    else if (vad_state == VAD_SILENCE && s_application->state == DEVICE_LISTENING)
    {
        protocol_send_stop_listening(s_application->protocol);
        application_set_state(DEVICE_IDLE);
    }
}

static void application_llm_handler(char *text)
{
    char *msg = NULL;
    asprintf(&msg, "LLM: %s", text);
    application_alert(msg);
    free(msg);
}

static void application_stt_handler(char *text)
{
    char *msg = NULL;
    asprintf(&msg, "User speaking: %s", text);
    application_alert(msg);
    free(msg);
}

static void application_iot_handler(char *text)
{
    thing_list_invoke(s_application->thing_list, (cJSON *)text);
}

static void application_tts_sentence_start_handler(char *text)
{
    char *msg = NULL;
    asprintf(&msg, "Xiaozhi speaking: %s", text);
    application_alert(msg);
    free(msg);
}

static void application_tts_start_handler(void)
{
    application_set_state(DEVICE_SPEAKING);
}

static void application_tts_stop_handler(void)
{
    application_set_state(DEVICE_IDLE);
}

static void application_audio_in_handler(void *buf, size_t len)
{
    ESP_LOGD(TAG, "Audio in: %u", len);
    audio_processor_write(buf, len);
}

static void application_audio_channel_close_handler(void)
{
    application_set_state(DEVICE_IDLE);
    audio_processor_reset_wakenet();
}

void application_init(void)
{
    s_application = malloc(sizeof(application_t));
    assert(s_application);
    s_application->state = DEVICE_STARTING;

    bsp_init();
    application_ota();

    // 获取thing_list并添加thing
    s_application->thing_list = thing_list_new();
    thing_list_add(s_application->thing_list, speaker_thing_create());

    // 初始化音频处理模块
    audio_processor_init();
    audio_processor_set_wakenet_callback(application_wakenet_handler, NULL);
    audio_processor_set_vad_callback(application_vad_handler, NULL);

    // 初始化协议模块
    s_application->protocol = protocol_create(PROTOCOL_TRANSPORT_TYPE_WEBSOCKET);
    protocol_set_audio_channel_close_callback(s_application->protocol, application_audio_channel_close_handler);
    protocol_set_audio_in_callback(s_application->protocol, application_audio_in_handler);
    protocol_set_llm_callback(s_application->protocol, application_llm_handler);
    protocol_set_stt_callback(s_application->protocol, application_stt_handler);
    protocol_set_tts_sentence_start_callback(s_application->protocol, application_tts_sentence_start_handler);
    protocol_set_tts_start_callback(s_application->protocol, application_tts_start_handler);
    protocol_set_tts_stop_callback(s_application->protocol, application_tts_stop_handler);
    protocol_set_iot_callback(s_application->protocol, application_iot_handler);

    xTaskCreate(application_audio_upload_task, "audio_upload_task", 4096, NULL, 5, &s_application->audio_upload_task);

    audio_processor_start();
    application_set_state(DEVICE_IDLE);
}